﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using CommonTypes.Server;
using Server.Model;

namespace Server.Services
{
    public class BullyProtocol
    {
        private BullyProtocol( )
        {
        }

        public static bool addServer( ServerData serverData )
        {
            System.Console.WriteLine( "I'm going to obtain the current primary" );
            String currentPrimary = obtainPrimary( serverData );
            System.Console.WriteLine( "The current primary is " + currentPrimary );

            if( currentPrimary == null )
                return startBully( serverData );
            else
            {
                System.Console.WriteLine( "I'm going to synchronize with the server" );

                //Synchronize with primary server
                MergingManager.Synchronize( currentPrimary, serverData );

                return startBully( serverData );
            }
        }

        /// <summary>
        /// This method uses regex expressions to extract the id number from the serverId, since a Id is
        /// server + number
        /// </summary>
        /// <returns>The id number</returns>
        /// <param name="serverId">The serverId to extract the id number</param>
        /// <remarks>This method throwns the exception ArgumentException if the serverId has a invalid format.</remarks>
        public static int getServerIdNumber( String serverId )
        {
            int idNum;
            String[ ] allNumbers = Regex.Split( serverId, @"\D+" );

            foreach( String possibleNumber in allNumbers )
            {
                if( Int32.TryParse( possibleNumber, out idNum ) )
                    return idNum;
            }

            throw new ArgumentException( "The serverId has a invalid format." );
        }

        public static bool startBully( ServerData serverData )
        {
            System.Console.WriteLine( "I'm going to try to be primary" );
            System.Console.WriteLine( "I'm in bullying state" );

            BullyResponse amIPrimary = BullyResponse.OK;
            int myID = getServerIdNumber( serverData.ServerId );
            System.Console.WriteLine( "My Id number is " + myID );

            bool exit = true;

            while( exit )
            {
                //Since this is a sorted dictionary, all the keys and therefore values are ordered
                foreach( KeyValuePair<String, IServerToServer> servers in serverData.ServersInformation.ServersProxy )
                {
                    if( BullyProtocol.getServerIdNumber( servers.Key ) > myID )
                        continue;
                    System.Console.WriteLine( String.Format( "I'm going to contact with server {0}", servers.Key ) );
                    try
                    {
                        amIPrimary = servers.Value.ElectPrimary( serverData.ServerId );
                        if( amIPrimary == BullyResponse.NO )
                            break;
                    }
                    catch
                    {
                        System.Console.WriteLine( String.Format( "Server {0} is down!", servers.Key ) );
                        exit = false;
                        continue;
                    }
                }

                if( amIPrimary != BullyResponse.FRESH_SERVER )
                    break;
                Thread.Sleep( 200 );
            }

            System.Console.WriteLine( "The voting result is " + amIPrimary );

            if( amIPrimary == BullyResponse.OK )
            {
                System.Console.WriteLine( "I'm going to be primary!" );
                foreach( KeyValuePair<String, IServerToServer> servers in serverData.ServersInformation.ServersProxy )
                {
                    if( BullyProtocol.getServerIdNumber( servers.Key ) < myID )
                        continue;
                    System.Console.WriteLine( "I'm going to tell that to server " + servers.Key );
                    try
                    {
                        servers.Value.AcknowledgePrimary( serverData.ServerId );
                        System.Console.WriteLine( "I've told that to server " + servers.Key );
                    }
                    catch
                    {
                        System.Console.WriteLine( String.Format( "Server {0} is down!", servers.Key ) );
                        continue;
                    }
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        public static String obtainPrimary( ServerData serverData )
        {
            String primary = null;

            foreach( KeyValuePair<String, IServerToServer> servers in serverData.ServersInformation.ServersProxy )
            {
                if( !servers.Key.Equals( serverData.ServerId ) )
                {
                    try
                    {
                        primary = servers.Value.currentPrimaryId( );
                        System.Console.WriteLine( String.Format( "For server {0} current primary is {1}", servers.Key, primary ) );
                        if( primary != null )
                            break;
                    }
                    catch
                    {
                        System.Console.WriteLine( String.Format( "Server {0} is down!", servers.Key ) );
                        continue;
                    }
                }
            }

            return primary;
        }

        public static BullyResponse electPrimary( string serverId, ServerData serverData )
        {
            switch( serverData.State )
            {
                case ServerState.ADDREMOVE:
                    return BullyResponse.FRESH_SERVER;
                case ServerState.BULLYING:
                    return normalElection( serverId, serverData );
                case ServerState.ESTABLISHED:
                    return normalElection( serverId, serverData );
                default:
                    return BullyResponse.NO;
            }
        }

        private static BullyResponse normalElection( string serverId, ServerData serverData )
        {
            int serverIdNum = BullyProtocol.getServerIdNumber( serverId );
            int myID = BullyProtocol.getServerIdNumber( serverData.ServerId );

            if( serverIdNum > myID )
            {
                System.Console.WriteLine( String.Format( "I'm going to vote no to server {0}", serverId ) );
                serverData.ServersInformation.ActiveServers[ serverId ] = true;
                return BullyResponse.NO;
            }
            else
            {
                System.Console.WriteLine( String.Format( "I'm going to vote yes to server {0}", serverId ) );
                return BullyResponse.OK;
            }
        }

        private delegate void PingProtocol( bool StartAsPrimary, AutoResetEvent ev, ServerData serverData );
        private delegate void SwapSides( String serverId, ServerData serverData );

        public static void AcknowledgePrimary( String serverId, ServerData serverData )
        {
            SwapSides sides = swapSides;
            sides.BeginInvoke( serverId, serverData, null, null );
        }

        private static void swapSides( String serverId, ServerData serverData )
        {
            if( serverData.State != ServerState.ADDREMOVE )
            {
                if( serverData.ServersInformation.Primary )
                {
                    serverData.Terminate.Set( );
                    serverData.IsTerminated.WaitOne( );
                    serverData.ServersInformation.PrimaryId = serverId;
                    serverData.ServersInformation.Primary = false;

                    foreach( String servers in serverData.ServersInformation.ServersLocation.Keys )
                    {
                        if( servers != serverData.ServerId )
                            serverData.ServersInformation.ActiveServers[ servers ] = true;
                    }

                    System.Console.WriteLine( "[SwapSides] I'm an secondary!" );
                    serverData.PingInformation.PingReceived = true;
                    serverData.PingInformation.NumberOfTries = 0;
                    serverData.State = ServerState.ESTABLISHED;
                    PingProtocol proto = PingManager.managePingProtocol;
                    Thread.Sleep( 200 );
                    proto.BeginInvoke( false, serverData.Terminate, serverData, null, null );
                }
                else
                {
                    serverData.ServersInformation.PrimaryId = serverId;
                    serverData.ServersInformation.Primary = false;
                    serverData.PingInformation.PingReceived = true;
                    serverData.PingInformation.NumberOfTries = 0;
                }
            }
        }
    }
}